package group

import (
	"io"
	"sync"
)

// PoolData 缓冲对象
type PoolData interface {
	Conn
	Put()
}

// Pool 缓冲池
type Pool interface {
	Init(n int)
	Get() (v PoolData)
	ForEach(f func(in PoolData))
	Close()
}

// poolData 缓冲对象
type poolData struct {
	t     int64       // 时间戳
	value interface{} // 值
	look  bool        // 占用标记
	pool  *pool
}

// Time 时间戳
func (pd *poolData) Time() int64 {
	return pd.t
}

// Value 得到值
func (pd *poolData) Value() interface{} {
	return pd.value
}

// Put 放入
// 缓冲对象重新放入 缓冲池
func (pd *poolData) Put() {
	// 断言 Value 接口 实现 io.Close
	var err error
	if clpse, ok := pd.value.(io.Closer); ok {
		err = clpse.Close()
	}
	// 还原标记
	pd.pool.mutex.Lock()
	pd.t = -1
	pd.look = false
	pd.pool.mutex.Unlock()
	// 通知等待
	select {
	case (*(pd.pool)).pchan <- struct{}{}: // 有等待则取消等待
	default:
	}
	// 通知结束
	if err != nil {
		panic(err)
	}
}

// pool 缓冲池
type pool struct {
	New     func() interface{} // 用于创建对象
	mutex   sync.Mutex         // 并发锁
	pchan   chan struct{}      // 缓冲对象通道
	pcache  []*poolData        // 对象列表
	isClose bool
}

// Init 初始化
func (p *pool) Init(n int) {
	p.isClose = true
	p.pchan = make(chan struct{}, 0)
	p.pcache = make([]*poolData, n)
	for i := 0; i < n; i++ {
		p.pcache[i] = &poolData{value: p.New(), pool: p, t: -1}
	}
}

// Get 取出一个对象
func (p *pool) Get() (v PoolData) {
Get:
	if !p.isClose {
		return
	}
	// 上锁
	p.mutex.Lock()
	// 历遍数据
	v = nil
	for i, n := 0, len(p.pcache); i < n; i++ {
		if !p.pcache[i].look {
			p.pcache[i].look = true // 标记占用
			p.pcache[i].t = Ts()    // 标记取出时间
			v = p.pcache[i]         // 返回取出对象
			break
		}
	}
	p.mutex.Unlock()
	if v != nil {
		return v
	}
	// 等待取出
	select {
	case <-p.pchan: // 如果没有取出则等待
	}
	goto Get
}

// Close 关闭
func (p *pool) Close() {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	p.isClose = false
	close(p.pchan)
}

// ForEach 历遍
func (p *pool) ForEach(f func(in PoolData)) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	for i, n := 0, len(p.pcache); i < n; i++ {
		f(p.pcache[i])
	}
}

// NewPool 创建缓冲池
func NewPool(New func() interface{}) Pool {
	return &pool{New: New}
}
